Kamal 2, Bye-bye Sprockets, and so much more! | This Week in Rails
railtiesに関する変更です
Kamalのバージョン2系を使用するようになりました Kamalは以前にも紹介したアプリケーションデプロイツールです
バージョン2系ではauto-SSLとマルチアプリケーションのデプロイが可能になるようですね
ActionPackに関する変更です
コントローラーに対して複数パターンのレートリミットを設定できるようになりました
前提として rate_limit メソッドがあります
次の例はSessionsコントローラのcreateアクションへのリクエストを3分間に10回までとする例です
code:rb
class SessionsController < ApplicationController
rate_limit to: 10, within: 3.minutes, only: :create
def create
# ...
end
end
3分間で11回目以降のリクエストが来た場合は 429 Too Many Requests をレスポンスします
この rate_limit メソッドですが、リクエストの回数のキャッシュをコントローラとアクションの組み合わせでカウントしているため、同じアクションに対して2種類以上のレートリミットを設定すると、1回のリクエスト時に複数回カウントアップしてしまう、という挙動になっていました
code:rb
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
gem "rails"
end
require "action_controller/railtie"
class TestApp < Rails::Application
config.root = __dir__
config.hosts << "example.org"
config.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
Rails.logger = config.logger
routes.draw do
get "/" => "test#index"
end
end
class TestController < ActionController::Base
self.cache_store = ActiveSupport::Cache::MemoryStore.new
rate_limit to: 2, within: 1.minute # short-term limit
rate_limit to: 5, within: 1.hour # long-term limit
def index
render plain: "Home"
end
end
require "minitest/autorun"
require "rack/test"
require 'active_support/testing/time_helpers'
class BugTest < Minitest::Test
include Rack::Test::Methods
include ActiveSupport::Testing::TimeHelpers
def test_rate_limits_are_independent
freeze_time do
# Assuming we have 0 request logged in short-term rate-limit
# Assuming we have 0 request logged in long-term rate-limit
# Make 1 request
get "/"
assert last_response.ok?
# Assuming we can make yet 1 out of 2 requests for short-term limit
# Assuming we can make yet 4 out of 5 requests for long-term limit
# Make second request
get "/"
assert last_response.ok? # failed
end
end
private
def app
Rails.application
end
end
この例ではTestコントローラについて、2種類のレートリミットを設定しています
1つ目は「1分間に2回まで」、2つ目は「1時間に5回まで」です
1回目のリクエストの際にはレートリミットに引っかかりません
2回目のリクエストの際も、レートリミットに引っかからないことが期待されます
「1分間に2回まで」をギリギリ違反していないためです
が、実際の挙動では2回目のリクエストで 429 Too Many Requests をレスポンスします
これは1回目のリクエストの際にリクエストの回数を rate_limit の呼び出しの回数だけ、すなわち2回カウントアップしてしまうためです
そこで今回のプルリクエストでは、rate_limit メソッドにオプションで name を指定できるようにし、この name を含めたキャッシュキーによってリクエスト回数を管理するようにしています
具体的には次のようなコードです
code:rb
class TestController < ActionController::Base
rate_limit to: 2, within: 1.minute, name: 'short-term'
rate_limit to: 5, within: 1.hour, name: 'long-term'
def index
render plain: "Home"
end
end
これにより、複数の種類のレートリミットを指定しても期待通りに動作するようになりました
ActionPackに関する変更です
より安全でより明示的なパラメータ処理の方法が導入されました
これまでコントローラーにおけるパラメータの処理といえば、requireとpermitを組み合わせる方法が一般的かと思います
具体的には params.require(:person).permit(:name, :age, pets: [:name]) のような記載です
今回のプルリクエストでは新たに expect メソッドが追加されました
code:rb
# If the url is altered to ?person=hacked
# Before
params.require(:person).permit(:name, :age, pets: :name) # raises NoMethodError, causing a 500 and potential error reporting
# After
params.expect(person: [ :name, :age, pets: :name ])
# raises ActionController::ParameterMissing, correctly returning a 400 error
リクエストパラメータをより厳密に処理できるようになるのは嬉しいですね
またリクエストパラメータから:idを取得する、といったよくあるケースでも expect メソッドを使うとより厳密になるという紹介がされていました
code:rb
# Before
User.find(params.require(:id)) # allows an array, altering behavior
# After
User.find(params.expect(:id)) # expect only returns non-blank permitted scalars (excludes Hash, Array, nil, "", etc)
requireをつかうとArrayが来るケースも想定する必要がありますが、expectを使えばblankではないスカラ値だけを受け取ることができます
その後の処理が行ないやすくなりそうですね